﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Reflection;

namespace Raider.WinForms.Actions.Linkage
{
    /// <summary>
    /// Links event of the target object with the event handler defined in the action.
    /// </summary>
    public class ActionEventLinker : ActionMemberLinker
    {
        protected static readonly Dictionary<Type, Dictionary<string, EventInfo>> TargetEventCache =
            new Dictionary<Type, Dictionary<string, EventInfo>>();

        public ActionEventLinker(MethodInfo actionMethod, string targetEvent, IList<Type> targetTypes)
            : base(actionMethod, targetTypes)
        {
            TargetEvent = targetEvent;
        }

        public string TargetEvent
        {
            get;
            private set;
        }

        public override bool LinkMember(IComponent client, IAction action)
        {
            Type targetType = client.GetType();
            
            if (!IsClientSupported(targetType))
                return false;

            MethodInfo actionMethod = (MethodInfo)ActionMember;
            EventInfo targetEvent = GetCachedTargetEvent(targetType, TargetEvent);

            if (targetEvent == null)
            {
                targetEvent = targetType.GetEvent(TargetEvent);

                if (targetEvent == null)
                    throw new InvalidOperationException("Client object of type " + targetType +
                                                        " does not have an event named " +
                                                        TargetEvent);

                TargetEventCache[targetType].Add(TargetEvent, targetEvent);
            }

            Delegate handler = Delegate.CreateDelegate(targetEvent.EventHandlerType, action, actionMethod);
            targetEvent.AddEventHandler(client, handler);

            return true;
        }

        public override bool UnLinkMember(IComponent client, IAction action)
        {
            Type targetType = client.GetType();

            if (!IsClientSupported(targetType))
                return false;

            MethodInfo actionMethod = (MethodInfo)ActionMember;
            EventInfo targetEvent = GetCachedTargetEvent(targetType, TargetEvent);

            if (targetEvent == null)
            {
                targetEvent = targetType.GetEvent(TargetEvent);

                if (targetEvent == null)
                    throw new InvalidOperationException("Client object of type " + targetType +
                                                        " does not have an event named " +
                                                        TargetEvent);

                TargetEventCache[targetType].Add(TargetEvent, targetEvent);
            }

            Delegate handler = Delegate.CreateDelegate(targetEvent.EventHandlerType, action, actionMethod);
            targetEvent.RemoveEventHandler(client, handler);

            return true;
        }

        protected static EventInfo GetCachedTargetEvent(Type targetType, string targetEvent)
        {
            Dictionary<string, EventInfo> targetMembers;
            if (TargetEventCache.TryGetValue(targetType, out targetMembers))
            {
                EventInfo member;
                if (targetMembers.TryGetValue(targetEvent, out member))
                {
                    return member;
                }
            }
            else
            {
                TargetEventCache.Add(targetType, new Dictionary<string, EventInfo>());
            }

            return null;
        }
    }
}
